package org.spiderflow.common.utils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;

/**
 * 解决 synchronized String 的问题
 * https://stackoverflow.com/questions/133988/synchronizing-on-string-objects-in-java
 * <p>
 * 1. 支持 Reentrant 即同一线程对同一个字符串进行Lock
 * 2. 支持同一线程，对多个不同的字符串加锁
 *
 * @author 张中海
 */
public class StringLock {
    private static final HashMap<String, LockCounter> LOCKER_MAP = new HashMap<>();
    private static final HashSet<Thread> WAITED_THREADS = new HashSet<>();

    private StringLock() {
    }

    public static void lock(String name) throws InterruptedException {
        synchronized (LOCKER_MAP) {
            LockCounter lockCounter = LOCKER_MAP.get(name);
            if (lockCounter != null && lockCounter.increment()) {
                return;
            }
            while ((lockCounter = LOCKER_MAP.get(name)) != null) {
                checkDeadLockBeforeWait();
                //记录 被等待的线程
                WAITED_THREADS.add(lockCounter.thread);
                // wait for release
                LOCKER_MAP.wait();
            }
            LOCKER_MAP.put(name, new LockCounter());
        }
    }

    /**
     * 检查死锁，例如
     * thread1: lock("a");lock("b");
     * thread2: lock("b");lock("a");
     */
    @SuppressWarnings("squid:S2273")
    private static void checkDeadLockBeforeWait() throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        // 检查当前线程被等待,而当前线程也将进入等待状态，因此是死锁
        if (WAITED_THREADS.contains(currentThread)) {
            LOCKER_MAP.entrySet().removeIf(entry -> entry.getValue().thread == currentThread);
            LOCKER_MAP.notifyAll();
            WAITED_THREADS.remove(currentThread);
            throw new InterruptedException("出现死锁，请检查代码");
        }
    }

    public static boolean tryLock(String name) throws InterruptedException {
        return tryLock(name, -1, TimeUnit.MINUTES);
    }

    public static boolean tryLock(String name, long timeout, TimeUnit timeUnit) throws InterruptedException {
        if (timeout == 0) {
            lock(name);
            return true;
        }

        synchronized (LOCKER_MAP) {
            LockCounter lockCounter = LOCKER_MAP.get(name);
            if (lockCounter != null && lockCounter.increment()) {
                return true;
            }
            if (timeout < 0) {
                //仅仅进行tryLock 不等待
                return false;
            }

            long localTimeout = timeUnit.toMillis(timeout);
            while (LOCKER_MAP.get(name) != null) {
                if (localTimeout <= 0) {
                    return false;
                }

                long l = System.currentTimeMillis();
                LOCKER_MAP.wait(localTimeout);
                localTimeout = localTimeout - (System.currentTimeMillis() - l);
            }
            LOCKER_MAP.put(name, new LockCounter());
            return true;
        }
    }

    public static void unlock(String name) {
        synchronized (LOCKER_MAP) {
            LockCounter lockCounter = LOCKER_MAP.get(name);
            if (lockCounter == null) {
                return;
            }
            if (lockCounter.decrement() == 0) {
                WAITED_THREADS.remove(lockCounter.thread);
                LOCKER_MAP.remove(name);
                LOCKER_MAP.notifyAll();
            }
        }
    }

    private static class LockCounter {
        private int count;
        private final Thread thread;

        private LockCounter() {
            count = 1;
            thread = Thread.currentThread();
        }

        private boolean increment() {
            if (thread == Thread.currentThread()) {
                count += 1;
                return true;
            }
            return false;
        }

        private int decrement() {
            if (thread == Thread.currentThread()) {
                count -= 1;
            }
            return count;
        }
    }
}
